package callgraph.process;

import callgraph.domain.JarConfig;
import callgraph.domain.ProfilingFilter;
import callgraph.dto.ClassInterfaceMethodInfo;
import callgraph.util.CommonUtil;
import callgraph.util.JavaCGConstants;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.BufferedWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

class PreHandleJar extends CallGraphProcessor {

    private static final String RUNNABLE_CLASS_NAME = Runnable.class.getName();
    private static final String CALLABLE_CLASS_NAME = Callable.class.getName();
    private static final String THREAD_CLASS_NAME = Thread.class.getName();

    public PreHandleJar(CallGraphProcessStorage storage, ProfilingFilter profilingFilter, JarConfig jarConfig, JarFile jarFile, BufferedWriter resultWriter) {
        super(storage, profilingFilter, jarConfig, jarFile, resultWriter);
    }

    @Override
    public boolean handle() {
        writeResult("J:" + jarNum.get() + " " + jarConfig.getJarUrl());
        writeResult(JavaCGConstants.NEW_LINE);
        Enumeration<JarEntry> enumeration = jarFile.entries();
        while (enumeration.hasMoreElements()) {
            JarEntry jarEntry = enumeration.nextElement();
            if (jarEntry.isDirectory() || !jarEntry.getName().endsWith(".class")) {
                continue;
            }
            if (!profilingFilter.needInject(jarEntry.getName())) {
                continue;
            }
            try {
                preHandleOneClass(jarConfig.getJarUrl(), jarEntry);
            } catch (IOException e) {
                e.printStackTrace();
                return false;
            }
        }
        return true;
    }

    private void preHandleOneClass(String jarFilePath, JarEntry jarEntry) throws IOException {
        ClassParser cp = new ClassParser(jarFilePath, jarEntry.getName());
        JavaClass javaClass = cp.parse();
        handleClass(javaClass);
        handleInterface(javaClass);
        handleExtends(javaClass);
    }

    private void handleClass(JavaClass javaClass) {
        if (javaClass.isClass()) {
            String className = javaClass.getClassName();
            String[] interfaceNames = javaClass.getInterfaceNames();
            Method[] methods = javaClass.getMethods();
            if (!storage.getClassInterfaceMethodInfoMap().containsKey(className)
                    && ArrayUtils.isNotEmpty(interfaceNames) && ArrayUtils.isNotEmpty(methods)) {
                ClassInterfaceMethodInfo info = ClassInterfaceMethodInfo.builder()
                        .interfaceNameList(strAry2List(interfaceNames))
                        .methodWithArgsList(CommonUtil.genImplClassMethodWithArgs(methods))
                        .build();
                storage.getClassInterfaceMethodInfoMap().put(className, info);
                handleRunnable(javaClass, info);
            }
        }
    }

    private void handleRunnable(JavaClass javaClass, ClassInterfaceMethodInfo info) {
        if (!javaClass.isAbstract()) {
            String className = javaClass.getClassName();
            if (info.getInterfaceNameList().contains(RUNNABLE_CLASS_NAME)) {
                storage.getRunnableImplClassMap().put(className, Boolean.FALSE);
            }
            if (info.getInterfaceNameList().contains(CALLABLE_CLASS_NAME)) {
                storage.getCallableImplClassMap().put(className, Boolean.FALSE);
            }
        }

    }

    private void handleInterface(JavaClass javaClass) {
        if (javaClass.isInterface()) {
            String className = javaClass.getClassName();
            Method[] methods = javaClass.getMethods();
            if (!storage.getInterfaceMethodWithArgsMap().containsKey(className) && ArrayUtils.isNotEmpty(methods)) {
                storage.getInterfaceMethodWithArgsMap().put(className, CommonUtil.genInterfaceAbstractMethodWithArgs(methods));
            }
        }
    }

    private void handleExtends(JavaClass javaClass) {
        String superClassName = javaClass.getSuperclassName();
        if (StringUtils.equals(THREAD_CLASS_NAME, superClassName)) {
            storage.getThreadChildClassMap().put(javaClass.getClassName(), Boolean.FALSE);
        }
        if (profilingFilter.needInject(superClassName)) {
            storage.getExtendsClassesSet().add(javaClass.getClassName());
            storage.getExtendsClassesSet().add(superClassName);
        }
    }

    private List<String> strAry2List(String[] strAry) {
        List<String> list = new ArrayList<>(strAry.length * 2);
        list.addAll(Arrays.asList(strAry));
        return list;
    }

    @Override
    public CallGraphProcessor next() {
        return new HandleJar(storage, profilingFilter, jarConfig, jarFile, resultWriter);
    }
}
